'''
Created on 2018年2月12日

@author: wesley.wei
'''
import os

_DELETED = b"\x01"
_OKAY = b"\x02"

class BinaryRecordFile:
    
    def __init__(self, filename, record_size, auto_flush=True):
        self.__record_size = record_size + 1
        mode = "w+b" if not os.path.exists(filename) else "r+b"
        self.__fh = open(filename, mode)
        self.auto_flush = auto_flush
    
    @property
    def record_size(self):
        return self.__record_size
    
    @property
    def name(self):
        return self.__fh.name()
    
    
    def flush(self):
        self.__fh.flush()
        
    def close(self):
        self.__fh.close()    
    
    def __setitem__(self, index ,record):
        assert isinstance(record, (bytes, bytearray)), "binary data is required"        
        assert len(record) == self.record_size, ("record must be exactly {0}".format(self.record_size))
        self.__fh.seek(index * self.__record_size)
        self.__fh.write(_OKAY)
        self.__fh.write(record)
        if self.auto_flush:
            self.__fh.auto_flush()
    
    
    
    def __seek_to_index(self,index):
        if self.auto_flush:
            self.__fh.flush()
        self.__fh.seek(0,os.SEEK_END)
        end = self.__fh.tell()
        offset = index * self.__record_size
        if offset > end:
            raise IndexError("no record at index postion {0}".format(index))
        self.__fh.seek(offset)
    
    
    
    def __getitem__(self,index):
        self.__seek_to_index(index)
        state = self.__fh.read(1)
        if state != _OKAY:
            return None
        return self.__fh.read(self.record_size)
            
    
    def __delitem__(self,index):
        self.__seek_to_index(index)
        state = self.__fh.read(1)
        if state != _OKAY:
            return
        self.__seek_to_index(index * self.__record_size)
        self.__fh.write(_DELETED)
        if self.auto_flush:
            self.__fh.flush()
        
        
    def undelete(self, index):
        self.__seek_to_index(index)
        state = self.__fh.read(1)
        if state == _DELETED:
            self.__fh.seek(index * self.__record_size)
            self.__fh.write(_OKAY)
            if self.auto_flush:
                self.__fh.flush()
            return True
        return False
        
            
    def __len__(self):
        if self.auto_flush:
            self.__fh.flush()
        self.__fh.seek(0, os.SEEK_END)
        end = self.__fh.tell()
        return end // self.__record_size
            
            

    '''
    第一种压缩文件的实现 ，把空白和已有删除标示的数据删除
    '''
    def inplace_compact(self):
        index = 0
        length = len(self)
        while index < length:
            self.__seek_to_index(index)
            state = self.__fh.read(1)
            if state != _OKAY:
                for next in range(index + 1, length):
                    self.__seek_to_index(next)
                    state = self.__fh.read(1)
                    if state == _OKAY:
                        self[index] = self[next]
                        del self[next]
                        break
                    else:
                        break
        index += 1
        self.__seek_to_index(0)
        state = self.__fh.read(1)
        if state != _OKAY:
            self.__fh.truncate(0)
        else:
            limit = None
            for index in range(len(self) - 1, 0, -1):
                self.__seek_to_index(index)
                state = self.__fh.read(1)
                if state != _OKAY:
                    limit = index
                else:
                    break
            if limit is not None:
                self.__fh.truncate(limit * self.__record_size)
        self.__fh.flush()
         


    '''
    文件压缩第二张实现，生成一个备份文件，和一个新文件
    '''
    def compact(self, keep_backup=False):
        compactfile = self.__fh.name + ".$$$"
        backupfile = self.__fh.name + ".bak"
        self.__fh.flush()
        self.__fh.seek(0)
        fh = open(compactfile, "wb")
        while True:
            data = self.__fh.read(self.__record_size)
            if not data:
                break
            if data[:1] == _OKAY:
                fh.write(data)
        fh.close()
        self.__fh.close()
        os.rename(self.__fh.name, backupfile)
        os.rename(compactfile, self.__fh.name)
        if not keep_backup:
            os.remove(backupfile)
        self.__fh = open(self.__fh.name, "r+b")
        
        
        